package com.hall.items.service;

import com.hall.TripletProcessResult;
import com.hall.items.aggr.*;
import com.hall.items.msg.MsgSender;
import com.hall.items.repo.*;
import com.hall.weaponenhancement.aggr.GoldAccount;
import com.mbrs.aggr.MyUserBan;
import com.mbrs.service.LoginOrRegisterResult;
import com.mbrs.service.PairProcessResult;
import dml.accounting.aggr.TransferResult;
import dml.common.id.aggr.LongIdGenerator;
import dml.game.weapon.aggr.Weapon;
import dml.game.weapon.aggr.WeaponEquipType;
import dml.game.weapon.aggr.WeaponPack;
import dml.game.weapon.proc.*;
import dml.game.weapon.repo.WeaponArchiveRepository;
import dml.game.weapon.repo.WeaponEquipFactoryRepository;
import dml.game.weapon.repo.WeaponEquipRepository;
import dml.operation.sto.aggr.Command;
import erp.annotation.Process;
import erp.mongodb.MongodbRepository;
import erp.repository.interfaceimplementer.InterfaceRepositoryImplBuilder;
import erp.viewcached.ViewCachedRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class ItemsService {

    private MongoTemplate mongoTemplate;
    private MyWeaponEquipTypeRepository weaponEquipTypeRepository;
    private WeaponArchiveRepository weaponArchiveRepository;
    private MyWeaponRepository weaponRepository;
    private MongodbRepository<MyPlayerWeaponPacks, Long> myPlayerWeaponPacksRepository;
    private MyPlayerWeaponPackRepository weaponPackRepository;
    private WeaponPackProcesses weaponPackProcesses;
    private WeaponMultiPackProcesses weaponMultiPackProcesses;
    private WeaponProcesses weaponProcesses;
    private MongodbRepository<MyWeaponPackCombatProperties, Long> myWeaponPackCombatPropertiesRepository;
    @Autowired
    private MongodbRepository<MyWeaponProperties, Long> myWeaponPropertiesRepository;
    private MongodbRepository<MyWeaponCombatValue, Long> myWeaponCombatValueRepository;
    private WeaponEquipRepository<MyWeaponEquip, Long> myWeaponEquipRepository;
    private MongodbRepository<MyWeaponPackCombatValue, Long> myWeaponPackCombatValueRepository;
    private ExecutorService executorService = Executors.newCachedThreadPool();

    @Autowired
    private MsgSender msgSender;

    @Autowired
    public ItemsService(MongoTemplate mongoTemplate,
                        MyWeaponRepository weaponRepository) {
        this.mongoTemplate = mongoTemplate;
        weaponEquipTypeRepository = new MyWeaponEquipTypeRepository(mongoTemplate);
        weaponArchiveRepository = InterfaceRepositoryImplBuilder.build(WeaponArchiveRepository.class,
                new ViewCachedRepository<>(new MongodbRepository<>(mongoTemplate, MyWeaponArchive.class)));
        myPlayerWeaponPacksRepository = new MongodbRepository<>(
                mongoTemplate, MyPlayerWeaponPacks.class);
        this.weaponRepository = weaponRepository;
        weaponPackRepository = new MyPlayerWeaponPackRepository(mongoTemplate);
        myWeaponPackCombatPropertiesRepository = new MongodbRepository<>(
                mongoTemplate, MyWeaponPackCombatProperties.class);
        myWeaponCombatValueRepository = new MongodbRepository<>(
                mongoTemplate, MyWeaponCombatValue.class);
        myWeaponEquipRepository = InterfaceRepositoryImplBuilder.build(WeaponEquipRepository.class,
                new MongodbRepository<>(mongoTemplate, MyWeaponEquip.class));
        myWeaponPackCombatValueRepository = new MongodbRepository<>(
                mongoTemplate, MyWeaponPackCombatValue.class);

        Long maxPackId = weaponPackRepository.findMaxId();
        weaponPackProcesses = new WeaponPackProcesses(
                this.weaponRepository,
                weaponEquipTypeRepository,
                new MyWeaponEquipTypeGetterRepository(new MyWeaponEquipTypeGetter()),
                weaponPackRepository,
                new MyWeaponPackFactoryRepository(new MyPlayerWeaponPackFactory()),
                new MyWeaponPackIdGeneratorRepository(new LongIdGenerator(maxPackId == null ? 1
                        : (maxPackId + 1))));

        weaponMultiPackProcesses = new WeaponMultiPackProcesses(
                weaponPackProcesses,
                (WeaponEquipRepository) myWeaponEquipRepository,
                (WeaponEquipFactoryRepository) new MyWeaponEquipFactoryRepository(new MyWeaponEquipFactory()));

        Long maxWeaponId = weaponRepository.findMaxId();
        weaponProcesses = new WeaponProcesses(
                (WeaponArchiveRepository<?, ?>) weaponArchiveRepository,
                this.weaponRepository,
                new MyWeaponIdGeneratorRepository(new LongIdGenerator(
                        maxWeaponId == null ? 1 : (maxWeaponId + 1))));
    }

    @Process
    public MyEquipType createEquipType(int id, String code) {
        MyEquipType myEquipType = new MyEquipType();
        myEquipType.setId(id);
        myEquipType.setCode(code);
        weaponEquipTypeRepository.put(myEquipType);
        return myEquipType;
    }

    //    @Process(publish = true)
    @Process
    public MyWeaponArchive createWeaponArchive(int id, String code,
                                               int equipTypeId) {
        MyWeaponArchive weaponArchive = new MyWeaponArchive();
        weaponArchive.setId(id);
        weaponArchive.setCode(code);
        weaponArchive.setEquipType(weaponEquipTypeRepository
                .find(equipTypeId));
        weaponArchiveRepository.put(weaponArchive);
        return weaponArchive;
    }

    public List<MyEquipType> queryAllEquipType() {
        return mongoTemplate.findAll(MyEquipType.class);
    }

    public List<MyWeaponArchive> queryAllWeaponArchive() {
        return mongoTemplate.findAll(MyWeaponArchive.class);
    }

    //    @Process(listening = "com.mbrs.service.LoginService.loginOrRegister", publish = true)
    @Process
    public boolean createFirstPlayerWeaponPack(
            PairProcessResult<MyUserBan, LoginOrRegisterResult> loginOrRegisterResult) {
        MyUserBan ban = loginOrRegisterResult.getA();
        if (ban != null) {
            return false;
        }
        if (!loginOrRegisterResult.getB().isCreateNewUser()) {
            return false;
        }
        MyPlayerWeaponPacks myPlayerWeaponPacks = new MyPlayerWeaponPacks();
        myPlayerWeaponPacks.setId((long) loginOrRegisterResult.getB().getUser()
                .getId());
        MyPlayerWeaponPacks existsPacks = myPlayerWeaponPacksRepository
                .putIfAbsent(myPlayerWeaponPacks);
        if (existsPacks != null) {
            return false;
        }
        MyPlayerWeaponPack pack = weaponPackProcesses.createWeaponPack(
                loginOrRegisterResult.getB().getUser().getId(),
                new MyWeaponPackProperties(1));

        myPlayerWeaponPacks.addPackAndSetToCurrent(pack);
        return true;
    }

    //    @Process(listening = "com.sto.service.SceneTriggeredOperationService.sendCmd", publish = true, dontPublishWhenResultIsNull = true)
    @Process
    public MyPlayerWeaponPack createWeaponAndEquip(Command cmd) {
        if ("createWeaponAndEquip".equals(cmd.getName())) {
            int weaponArchiveId = Integer.valueOf(cmd
                    .getParameterValue("weaponArchiveId"));
            long playerId = Long.valueOf(cmd.getParameterValue("playerId"));

            MyWeapon weapon = weaponProcesses.createWeapon(weaponArchiveId,
                    playerId, null);
            Object currentPackId = null;
            MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                    .take(playerId);
            if (palyerPacks == null) {
                palyerPacks = new MyPlayerWeaponPacks();
                palyerPacks.setId(playerId);
                MyPlayerWeaponPacks existsPacks = myPlayerWeaponPacksRepository
                        .putIfAbsent(palyerPacks);
                if (existsPacks != null) {
                    palyerPacks = existsPacks;
                    currentPackId = palyerPacks.getCurrentPackId();
                }
            } else {
                currentPackId = palyerPacks.getCurrentPackId();
            }
            if (currentPackId == null) {
                MyPlayerWeaponPack pack = weaponPackProcesses.createWeaponPack(
                        playerId, new MyWeaponPackProperties(1));

                palyerPacks.addPackAndSetToCurrent(pack);
                currentPackId = pack.getId();
            }

            MyPlayerWeaponPack currentPack = weaponPackRepository
                    .take((Long) currentPackId);

            MultiPackEquipWeaponResult<Weapon, WeaponPack<WeaponEquipType, Weapon>> rslt = weaponMultiPackProcesses
                    .equipWeapon(
                            weapon.getId(),
                            palyerPacks.getCurrentPackId(),
                            playerId,
                            new MyWeaponEquipProperties(weapon, currentPack
                                    .getPackNum()));
            WeaponPack pack = rslt.getEquipWeaponResult().getPack();
            return (MyPlayerWeaponPack) pack;
        } else {
            return null;
        }
    }

    //    @Process(publish = true)
    @Process
    public MyWeapon createWeapon(int weaponArchiveId, long playerId) {
        MyWeapon weapon = weaponProcesses.createWeapon(weaponArchiveId,
                playerId, null);
        return weapon;
    }

    public MyPlayerWeaponPack queryCurrentPlayerWeaponPack(long playerId) {
        return weaponPackRepository.queryCurrentWeaponPack(playerId);
    }

    @Process
    public List<MyWeapon> queryPlayerWeapons(long playerId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("playerId").is(playerId));
        List<MyWeapon> weapons = mongoTemplate.find(query, MyWeapon.class);
        for (MyWeapon weapon : weapons) {
            weapon.setArchive((MyWeaponArchive) weaponArchiveRepository.find((Integer) weapon
                    .getArchive().getId()));
        }
        return weapons;
    }

    public List<MyWeaponEquip> queryPlayerWeaponEquips(long playerId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("weapon.playerId").is(playerId));
        return mongoTemplate.find(query, MyWeaponEquip.class);
    }

    //    @Process(publish = true)
    @Process
    public MultiPackEquipWeaponResult<MyWeapon, MyPlayerWeaponPack> equipWeapon(
            long playerId, long weaponId) {
        MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                .find(playerId);
        MyPlayerWeaponPack currentPack = weaponPackRepository
                .take(palyerPacks.getCurrentPackId());
        MyWeapon weapon = weaponRepository.find(weaponId);
        MultiPackEquipWeaponResult<MyWeapon, MyPlayerWeaponPack> rslt = weaponMultiPackProcesses
                .equipWeapon(weaponId, palyerPacks.getCurrentPackId(),
                        playerId, new MyWeaponEquipProperties(weapon,
                                currentPack.getPackNum()));
        return rslt;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.equipWeapon", publish = true)
    @Process
    public void updatePackCombatValueByEquipWeapon(
            MultiPackEquipWeaponResult<MyWeapon, MyPlayerWeaponPack> multiPackEquipWeaponResult) {
        EquipWeaponResult<MyWeapon, MyPlayerWeaponPack> equipWeaponResult = multiPackEquipWeaponResult
                .getEquipWeaponResult();
        if (equipWeaponResult != null) {
            calculatePackCombatValue((long) equipWeaponResult.getPack().getId());
        }
        UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> unloadWeaponResult = multiPackEquipWeaponResult
                .getUnloadWeaponResult();
        if (unloadWeaponResult != null) {
            calculatePackCombatValue((long) unloadWeaponResult.getPack()
                    .getId());
        }
    }

    //    @Process(publish = true)
    @Process
    public UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> unloadWeapon(
            long playerId, long weaponId) {
        MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                .find(playerId);
        UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> rslt = weaponMultiPackProcesses
                .unloadWeapon(weaponId, palyerPacks.getCurrentPackId(),
                        playerId);
        return rslt;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.unloadWeapon", publish = true)
    @Process
    public void updatePackCombatValueByUnloadWeapon(
            UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> unloadWeaponResult) {
        if (unloadWeaponResult != null) {
            calculatePackCombatValue((long) unloadWeaponResult.getPack()
                    .getId());
        }
    }

    public List<MyWeaponLevel> queryPlayerWeaponLevels(long playerId) {
        Query query = new Query();
        query.addCriteria(Criteria.where("weapon.playerId").is(playerId));
        return mongoTemplate.find(query, MyWeaponLevel.class);
    }

    public List<MyWeaponLevel> queryPlayerWeaponLevels(List<Long> weaponIds) {
        Query query = new Query();
        query.addCriteria(Criteria.where("id").in(weaponIds));
        return mongoTemplate.find(query, MyWeaponLevel.class);
    }

    //    @Process(listening = "com.hall.weaponenhancement.service.WeaponEnhancementService.enhanceWeapon", publish = true)
    @Process
    public MyWeaponPackCombatProperties updatePackCombatPropertiesByEnhanceWeapon(
            TripletProcessResult<MyWeaponLevel, MyWeaponProperties, TransferResult<GoldAccount>> enhanceWeaponResult) {
        Object weaponId = enhanceWeaponResult.getA().getId();
        long packId = (long) myWeaponEquipRepository.find((Long) weaponId)
                .getPackId();
        MyPlayerWeaponPack pack = weaponPackRepository.find(packId);
        if (!pack.getEquipedWeapons().hasWeapon(
                enhanceWeaponResult.getA().getId())) {
            return null;
        }
        MyWeaponPackCombatProperties rslt = updatePackCombatProperties(pack);
        return rslt;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.updatePackCombatPropertiesByEnhanceWeapon", publish = true)
    @Process
    public void updateAllWeaponsCombatValueByEnhanceWeapon(
            MyWeaponPackCombatProperties myWeaponPackCombatProperties) {
        MyPlayerWeaponPack pack = weaponPackRepository
                .find(myWeaponPackCombatProperties.getId());
        MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                .find((Long) pack.getPlayerId());
        if (!pack.getId().equals(palyerPacks.getCurrentPackId())) {
            return;
        }
        List<MyWeapon> weapons = weaponRepository.queryAllByField("playerId",
                pack.getPlayerId());
        for (MyWeapon weapon : weapons) {
            executorService.submit(() -> {
                updateWeaponCombatValue((long) pack.getId(),
                        (long) weapon.getId());
                msgSender.sendProcess(true);
            });
        }
    }

    //    @Process(publish = true, dontPublishWhenResultIsNull = true)
    @Process
    public MyWeaponCombatValue updateWeaponCombatValue(long packId,
                                                       long weaponId) {
        MyWeaponProperties myWeaponProperties = myWeaponPropertiesRepository
                .find(weaponId);
        MyWeaponPackCombatProperties packCombatProperties = myWeaponPackCombatPropertiesRepository
                .find(packId);
        MyWeaponCombatValue rslt = calculateWeaponCombatValue(myWeaponProperties,
                packCombatProperties);
        return rslt;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.updateWeaponCombatValue", publish = true)
    @Process
    public MyWeaponPackCombatValue updatePackCombatValue(
            MyWeaponCombatValue myWeaponCombatValue) {
        MyWeaponEquip myWeaponEquip = myWeaponEquipRepository
                .find(myWeaponCombatValue.getId());
        if (myWeaponEquip == null) {
            return null;
        }
        MyWeaponPackCombatValue myWeaponPackCombatValue = calculatePackCombatValue((long) myWeaponEquip
                .getPackId());
        return myWeaponPackCombatValue;
    }

    private MyWeaponPackCombatValue calculatePackCombatValue(long packId) {
        long combatValue = 0;
        MyPlayerWeaponPack pack = weaponPackRepository.find(packId);
        for (long wid : pack.getEquipedWeaponIds()) {
            MyWeaponCombatValue myWeaponCombatValue = myWeaponCombatValueRepository
                    .find(wid);
            if (myWeaponCombatValue != null) {
                combatValue += myWeaponCombatValue.getValue();
            }
        }
        MyWeaponPackCombatValue myWeaponPackCombatValue = myWeaponPackCombatValueRepository
                .takeOrPutIfAbsent(packId,
                        new MyWeaponPackCombatValue(packId));
        myWeaponPackCombatValue.setValue(combatValue);
        return myWeaponPackCombatValue;
    }

    //    @Process(listening = "com.hall.weaponenhancement.service.WeaponEnhancementService.enhanceWeapon", publish = true, dontPublishWhenResultIsNull = true)
    @Process
    public MyWeaponCombatValue updateWeaponCombatValueByEnhanceWeapon(
            TripletProcessResult<MyWeaponLevel, MyWeaponProperties, TransferResult<GoldAccount>> enhanceWeaponResult) {
        MyPlayerWeaponPacks weaponPacks = myPlayerWeaponPacksRepository
                .take((Long) enhanceWeaponResult.getA().getWeapon()
                        .getPlayerId());
        MyWeaponPackCombatProperties packCombatProperties = myWeaponPackCombatPropertiesRepository
                .find(weaponPacks.getCurrentPackId());
        Object weaponId = enhanceWeaponResult.getA().getId();
        MyWeaponProperties myWeaponProperties = myWeaponPropertiesRepository
                .find((Long) weaponId);
        if (myWeaponProperties == null || packCombatProperties == null) {
            return null;
        }
        return calculateWeaponCombatValue(myWeaponProperties,
                packCombatProperties);
    }

    private MyWeaponCombatValue calculateWeaponCombatValue(
            MyWeaponProperties myWeaponProperties,
            MyWeaponPackCombatProperties packCombatProperties) {
        if (myWeaponProperties == null || packCombatProperties == null) {
            return null;
        }
        MyWeaponCombatValue myWeaponCombatValue = myWeaponCombatValueRepository
                .takeOrPutIfAbsent(myWeaponProperties.getId(),
                        new MyWeaponCombatValue(myWeaponProperties));
        myWeaponCombatValue.setValue(myWeaponProperties.getAttack() * 1000
                + packCombatProperties.getAttack() * 10);
        return myWeaponCombatValue;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.equipWeapon", publish = true)
    @Process
    public MyWeaponPackCombatProperties updatePackCombatPropertiesByEquipWeapon(
            MultiPackEquipWeaponResult<MyWeapon, MyPlayerWeaponPack> equipWeaponResult) {
        MyPlayerWeaponPack pack = equipWeaponResult.getEquipWeaponResult()
                .getPack();
        pack = weaponPackRepository.find((Long) pack.getId());
        MyWeaponPackCombatProperties myWeaponPackCombatProperties = updatePackCombatProperties(pack);
        UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> unloadWeaponResult = equipWeaponResult
                .getUnloadWeaponResult();
        if (unloadWeaponResult != null) {
            pack = unloadWeaponResult.getPack();
            pack = weaponPackRepository.find((Long) pack.getId());
            updatePackCombatProperties(pack);
        }
        return myWeaponPackCombatProperties;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.updatePackCombatPropertiesByEquipWeapon", publish = true)
    @Process
    public void updateAllWeaponsCombatValueByEquipWeapon(
            MyWeaponPackCombatProperties myWeaponPackCombatProperties) {
        MyPlayerWeaponPack pack = weaponPackRepository
                .find(myWeaponPackCombatProperties.getId());
        MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                .find((Long) pack.getPlayerId());
        if (!pack.getId().equals(palyerPacks.getCurrentPackId())) {
            return;
        }
        List<MyWeapon> weapons = weaponRepository.queryAllByField("playerId",
                pack.getPlayerId());
        for (MyWeapon weapon : weapons) {
            executorService.submit(() -> {
                updateWeaponCombatValue((long) pack.getId(),
                        (long) weapon.getId());
                msgSender.sendProcess(true);
            });
        }
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.unloadWeapon", publish = true)
    @Process
    public MyWeaponPackCombatProperties updatePackCombatPropertiesByUnloadWeapon(
            UnloadWeaponResult<MyWeapon, MyPlayerWeaponPack> unloadWeaponResult) {
        MyPlayerWeaponPack pack = unloadWeaponResult.getPack();
        pack = weaponPackRepository.find((Long) pack.getId());
        MyWeaponPackCombatProperties rslt = updatePackCombatProperties(pack);
        return rslt;
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.updatePackCombatPropertiesByUnloadWeapon", publish = true)
    @Process
    public void updateAllWeaponsCombatValueByUnloadWeapon(
            MyWeaponPackCombatProperties myWeaponPackCombatProperties) {
        MyPlayerWeaponPack pack = weaponPackRepository
                .find(myWeaponPackCombatProperties.getId());
        MyPlayerWeaponPacks palyerPacks = myPlayerWeaponPacksRepository
                .find((Long) pack.getPlayerId());
        if (!pack.getId().equals(palyerPacks.getCurrentPackId())) {
            return;
        }
        List<MyWeapon> weapons = weaponRepository.queryAllByField("playerId",
                pack.getPlayerId());
        for (MyWeapon weapon : weapons) {
            executorService.submit(() -> {
                updateWeaponCombatValue((long) pack.getId(),
                        (long) weapon.getId());
                msgSender.sendProcess(true);
            });
        }
    }

    //    @Process(listening = "com.hall.items.service.ItemsService.createWeaponAndEquip", publish = true)
    @Process
    public void updatePackCombatPropertiesByCreateWeaponAndEquip(
            MyPlayerWeaponPack pack) {
        MyPlayerWeaponPack myPlayerWeaponPack = weaponPackRepository
                .find((Long) pack.getId());
        updatePackCombatProperties(myPlayerWeaponPack);
    }

    private MyWeaponPackCombatProperties updatePackCombatProperties(
            MyPlayerWeaponPack pack) {
        MyWeaponPackCombatProperties packCombatProperties = myWeaponPackCombatPropertiesRepository
                .takeOrPutIfAbsent((Long) pack.getId(),
                        new MyWeaponPackCombatProperties((Long) pack.getId()));
        int attack = 0;
        List<Long> equipedWeaponIds = pack.getEquipedWeaponIds();
        for (long weaponId : equipedWeaponIds) {
            MyWeaponProperties myWeaponProperties = myWeaponPropertiesRepository
                    .find(weaponId);
            if (myWeaponProperties == null) {
                continue;
            }
            attack += myWeaponProperties.getAttack();
        }
        packCombatProperties.setAttack(attack);
        return packCombatProperties;
    }

    @Process
    public MyWeaponPackCombatValue queryPackCombatValue(long packId) {
        return myWeaponPackCombatValueRepository.find(packId);
    }

}
